#include "include/cxxstd.h"
#include "include/config.h"
#include "ABIBase.h"

using namespace std;
using namespace zero;

// std implementation <([{
namespace std
{
exception::exception() throw()
{
}

exception::~exception() throw()
{
}

const char* exception::what() const throw()
{
	return "std::exception";
}

EXPORT_CXXDATA(_ZTVSt9exception);
EXPORT_CXXDATA(_ZTISt9exception);

bad_exception::bad_exception() throw()
{
}
EXPORT_CXXSYMBOL(_ZNSt13bad_exceptionC1Ev);
EXPORT_CXXSYMBOL(_ZNSt13bad_exceptionC2Ev);

bad_exception::~bad_exception() throw()
{
}
EXPORT_CXXSYMBOL(_ZNSt13bad_exceptionD1Ev);
EXPORT_CXXSYMBOL(_ZNSt13bad_exceptionD2Ev);

const char* bad_exception::what() const throw()
{
	return "std::bad_exception";
}

EXPORT_CXXDATA(_ZTVSt13bad_exception);
EXPORT_CXXDATA(_ZTISt13bad_exception);
}
// }])>

namespace zero
{
// exception ABI data. <([{
// sleb and uleb. <([{
typedef long sleb128_t;
typedef unsigned long uleb128_t;

const unsigned char* readUleb128(const unsigned char* p, uleb128_t* val)
{
	unsigned int shift = 0;
	unsigned char byte;
	uleb128_t result;

	result = 0;
	do {
		byte = *p++;
		result |= ((uleb128_t) byte & 0x7f) << shift;
		shift += 7;
	} while (byte & 0x80);

	*val = result;
	return p;
}

const unsigned char* readSleb128(const unsigned char* p, sleb128_t* val)
{
	unsigned int shift = 0;
	unsigned char byte;
	uleb128_t result;

	result = 0;
	do {
		byte = *p++;
		result |= ((uleb128_t) byte & 0x7f) << shift;
		shift += 7;
	} while (byte & 0x80);

	/* Sign-extend a negative value.  */
	if (shift < 8 * sizeof(result) && (byte & 0x40) != 0)
		result |= -(((uleb128_t) 1L) << shift);

	*val = (sleb128_t) result;
	return p;
}
// }])>

// section .eh_frame_hdr. <([{
struct ExFrameHeader
{
	unsigned char sign[4]; // = 0x1, 0x1b, 0x3, 0x3b.
	int ehFrameOffset;
	int fdeCount;
	struct {
		ptr_t initLoc;
		ptrdiff_t fdeOffset;
	} data[];
};
// }])>

// section .eh_frame. <([{
// fde_encoding = per_encoding = lsda_encoding = 0 (DW_EH_PE_absptr).
// fde_encoding = lsda_encoding = 0x1b, per_encoding = 0x9b. (DW_EH_PE_sdata4 = 0xb, DW_EH_PE_pcrel = 0x10, DW_EH_PE_indirect = 0x80).
struct CIE
{
	int size; // = 0x18, 0x1c.
	int cieIdentifierTag; // = 0.
	unsigned char version; // = 1.
	unsigned char augStr[0]; // == "zPL\0", "zPLR\0". `R' is outputted only when fde_encoding != DW_EH_PE_absptr.
	uleb128_t codeFactor; // = 1.
	sleb128_t dataFactor; // = -4.
	unsigned char retReg; // = 8.
	uleb128_t augSize; // == 0x6, 0x7. = sizeof(perEncode + perv0 + lsdaEncode + fdeEncode).
	unsigned char perEncode; // = 0, 0x9b (per_encoding).
	unsigned char perv0[4]; // = __gxx_personality_v0 or DW.ref.__gxx_personality_v0 - .
	unsigned char lsdaEncode; // = 0, 0x1b (lsda_encoding).
	unsigned char fdeEncode; // == , 0x1b (fde_encoding) output only when fde_encoding != DW_EH_PE_absptr.
	unsigned char cfi[3]; // = 0xc, 0x4, 0x4.
	unsigned char cfi2[2]; // = 0x88, 0x1.
} __attribute__((packed));

struct FDE
{
	int size;
	int cieOffset;
	ptr_t pcBegin; // absolute or relative address according to fde_encoding.
	long pcRange;
	unsigned char augSize; // = 4. = size_of_encoded_value(lsda_encoding).
	ptr_t lsda; // see pcBegin.
	// The first two cfis must be
	// 1) pushl %ebp // = 0x41, 0xe, 0x08, 0x85, 0x2.
	// 2) movl %esp, %ebp // = 0x42, 0xd, 0x5.
	// 3) pushl %esi, %edi, %ebx. // = 0x4X .... if = 0, no cfi.
	unsigned char cfis[];
} __attribute__((packed));

struct UnwindEx
{
	CIE cie;
	FDE fdes[];
};
// }])>

// section .gcc_except_table. <([{
struct CallSite {
	uleb128_t start;
	uleb128_t len;
	uleb128_t lp;
	uleb128_t action;
};

struct ActionRecord {
	sleb128_t filter;
	sleb128_t disp;
};

struct CxxEx
{
	unsigned char lpStart; // = 0xff (omit).
	unsigned char typeEncode; // = 0 or 0x9b (indirect pcrel sdata4).
	uleb128_t typeBaseOffset;
	unsigned char callSiteEncode; // = 1.
	uleb128_t callSiteLength;
	struct {
		struct CallSite callSite;
		struct ActionRecord actionRecord;
	} data[];

	// Later fields are used by __gxx_personality_v0.
	ptr_t codeBase;
	const struct CallSite* csBase;
	const struct ActionRecord* arBase;
	const std::type_info* const* tiBase;
} __attribute__((packed));
// }])>
// }])>

// Unwind Layer <([{
extern "C" bool __gxx_personality_v0(int, int, unsigned long long, struct UnwindException*, void*);
extern "C" void __cxa_free_exception(void* thrown_exception) throw();
extern "C" void* __cxa_begin_catch(struct UnwindException*) throw();

// ABI user data.
struct UnwindException
{
	void (*exception_cleanup) (int, struct UnwindException*);
};

struct UnwindContext
{
	// return frame.
	unsigned int esp;
	unsigned int ebp;
	unsigned char* ip;
	// exception return registers.
	unsigned int eax; // __builtin_eh_return_data_regno(0) = ue.
	unsigned int edx; // __builtin_eh_return_data_regno(1) = handler_switch_value.
	// registers need to be restored.
	unsigned int ebx;
	unsigned int esi;
	unsigned int edi;

	// Current fde and whether its ELF is pic type.
	const FDE* fde;
	bool pic;

	// CXX layer uses later members, need pic adjustment.
	ptr_t pcBegin;
	ptr_t lsda;

	void adjustPic(void);
	void restoreCFIs(void);
	void findFDE(ABIBase*);
} uc;

void UnwindContext::restoreCFIs(void)
{
	const unsigned char* p = fde->cfis + 9;
	const unsigned char* end = reinterpret_cast<const unsigned char*>(fde) + fde->size + 4;
	const unsigned int* stack = reinterpret_cast<unsigned int*>(ebp);
	stack--;
	for (int cnt = 3; p < end && cnt > 0; stack--, p += 2, cnt--) {
		if (*p == 0x83 || *p == 0x86 || *p == 0x87) {
			switch (*(p + 1)) {
				case 0x5:
					edi = *stack;
					break;
				case 0x4:
					esi = *stack;
					break;
				case 0x3:
					ebx = *stack;
					break;
			}
		}
	}
}

void UnwindContext::adjustPic(void)
{
	pcBegin = pic ?
		reinterpret_cast<ptr_t>(&fde->pcBegin) + fde->pcBegin
		: fde->pcBegin;
	if (fde->augSize == 0) // In the case, no lsda field at all, see gcc source.
		lsda = NULL;
	else
		lsda = pic ?
			reinterpret_cast<ptr_t>(&fde->lsda) + fde->lsda
			: fde->lsda;
}

void UnwindContext::findFDE(ABIBase* base)
{
	if (!base->include(reinterpret_cast<ptr_t>(ip))) {
		// Keep it in mind, os abibase is the bottom, so we assert prev != NULL.
		findFDE(base->getPrev());
		return;
	}
	ExFrameHeader* efh = reinterpret_cast<ExFrameHeader*>(base->exHeader.begin);
	int lo = 0, mid = 0, hi = efh->fdeCount;
	fde = NULL;
	pic = base->pic;
	while (lo < hi) {
		mid = (lo + hi) / 2;
		typeof(efh->data[0]) current = efh->data[mid];
		fde = reinterpret_cast<const struct FDE*>(reinterpret_cast<const unsigned char*>(efh) + current.fdeOffset);
		const unsigned char* begin = pic ?
			reinterpret_cast<const unsigned char*>(&fde->pcBegin) + fde->pcBegin :
			reinterpret_cast<unsigned char*>(fde->pcBegin);
		if (ip < begin)
			hi = mid;
		else if (ip >= begin + fde->pcRange)
			lo = mid + 1;
		else
			return;
	}
	return;
}

extern "C" void _Unwind_RaiseException(struct UnwindException* ue)
{
	// Current stack snapshot is foo >> __cxa_throw, __cxa_rethrow >> _Unwind_RaiseException.
	asm volatile ( \
		"movl %%ebp, %0\n\t" \
		"movl %%esp, %1\n\t" \
		"call 1f\n\t" \
		"1:\n\t" \
		"pop %2\n\t" \
		: "=q" (uc.ebp), "=q" (uc.esp), "=q" (uc.ip) \
		: \
		);
	uc.ip--;

	while (true) {
		uc.findFDE(ABIBase::top());
		if (uc.fde != NULL) {
			uc.adjustPic();
			if (__gxx_personality_v0(0, 0, 0, ue, NULL))
				break;
			uc.restoreCFIs();
		}
		uc.esp = uc.ebp + sizeof(unsigned int) * 2;
		asm volatile ( \
			"movl 4(%0), %1\n\t" \
			"movl (%0), %0\n\t" \
			: "=q" (uc.ebp), "=q" (uc.ip) \
			: "g" (uc.ebp) \
			);
		uc.ip--;
	}

	asm volatile ( \
		"movl 8(%%ecx), %%ebp\n\t" \
		"movl (%%ecx), %%esp\n\t" \
		"movl %%ebp, -4(%%esp)\n\t" \
		"movl 4(%%ecx), %%ebp\n\t" \
		"movl (%%ecx), %%esp\n\t" \
		"subl $4, %%esp\n\t" \
		"ret" \
		: \
		: "c" (&uc.esp),
		"a" (uc.eax), "b" (uc.ebx), "d" (uc.edx), "S" (uc.esi), "D" (uc.edi) \
		);
}

extern "C" void _Unwind_Resume(struct UnwindException* ue)
{
	// Current stack snapshot is foo >> _Unwind_RaiseException.
	asm volatile ( \
		"movl %%ebp, %0\n\t" \
		"movl %%esp, %1\n\t" \
		"call 1f\n\t" \
		"1:\n\t" \
		"pop %2\n\t" \
		: "=q" (uc.ebp), "=q" (uc.esp), "=q" (uc.ip) \
		: \
		);
	uc.ip--;

	while (true) {
		uc.findFDE(ABIBase::top());
		if (uc.fde != NULL) {
			uc.adjustPic();
			if (__gxx_personality_v0(0, 0, 0, ue, NULL))
				break;
			uc.restoreCFIs();
		}
		uc.esp = uc.ebp + sizeof(unsigned int) * 2;
		asm volatile ( \
			"movl 4(%0), %1\n\t" \
			"movl (%0), %0\n\t" \
			: "=q" (uc.ebp), "=q" (uc.ip) \
			: "g" (uc.ebp) \
			);
		uc.ip--;
	}

	asm volatile ( \
		"movl 8(%%ecx), %%ebp\n\t" \
		"movl (%%ecx), %%esp\n\t" \
		"movl %%ebp, -4(%%esp)\n\t" \
		"movl 4(%%ecx), %%ebp\n\t" \
		"movl (%%ecx), %%esp\n\t" \
		"subl $4, %%esp\n\t" \
		"ret" \
		: \
		: "c" (&uc.esp),
		"a" (uc.eax), "b" (uc.ebx), "d" (uc.edx), "S" (uc.esi), "D" (uc.edi) \
		);
}
EXPORT_CSYMBOL(_Unwind_Resume);

extern "C" void _Unwind_DeleteException(struct UnwindException* ue)
{
	if (ue->exception_cleanup)
		(*ue->exception_cleanup)(0, ue);
}
// }])>

// CXX Layer <([{
// ABI user data.
struct CXXException
{
	std::type_info* exceptionType;
	void (*exceptionDestructor)(void*);
	int count;

	UnwindException ue;
};

static struct
{
	CXXException* caughtException;
} ehGlobal; // @TODO@: the variable should be in TLS.

void __gxx_exception_cleanup(int, UnwindException* ue)
{
	CXXException* cxxe = containerof(ue, CXXException, ue);

	if (cxxe->exceptionDestructor)
		cxxe->exceptionDestructor(cxxe + 1);

	__cxa_free_exception(cxxe + 1);
}

extern "C" void __cxa_call_unexpected(void*)
{
	abort();
}

extern "C" void* __cxa_allocate_exception(size_t thrownSize) throw()
{
	thrownSize += sizeof(CXXException);
	void* ret = ABIBase::top()->newHandler(thrownSize);
	if (!ret)
		abort();

	return reinterpret_cast<void*>((reinterpret_cast<ptr_t>(ret) + sizeof(CXXException)));
}
EXPORT_CSYMBOL(__cxa_allocate_exception);

extern "C" void __cxa_free_exception(void* ptr) throw()
{
	ABIBase::top()->deleteHandler((char*) ptr - sizeof(CXXException));
}
EXPORT_CSYMBOL(__cxa_free_exception);

extern "C" void __cxa_throw(void* obj, type_info* tinfo, void (*dest)(void*))
{
	CXXException* cxxe = reinterpret_cast<CXXException*>(obj) - 1;
	cxxe->exceptionType = tinfo;
	cxxe->exceptionDestructor = dest;
	cxxe->ue.exception_cleanup = __gxx_exception_cleanup;
	_Unwind_RaiseException(&cxxe->ue);
	abort();
}
EXPORT_CSYMBOL(__cxa_throw);

extern "C" void __cxa_rethrow()
{
	CXXException* cxxe = ehGlobal.caughtException;
	cxxe->count = -cxxe->count;
	_Unwind_RaiseException(&cxxe->ue);
	abort();
}
EXPORT_CSYMBOL(__cxa_rethrow);

extern "C" void* __cxa_begin_catch(UnwindException* ue) throw()
{
	CXXException* cxxe = containerof(ue, CXXException, ue);
	ehGlobal.caughtException = cxxe;

	if (cxxe->count < 0)
		cxxe->count = -cxxe->count + 1;
	else
		cxxe->count++;

	return cxxe + 1;
}
EXPORT_CSYMBOL(__cxa_begin_catch);

extern "C" void __cxa_end_catch()
{
	CXXException* cxxe = ehGlobal.caughtException;

	if (cxxe->count < 0)
		cxxe->count++;
	else if (--cxxe->count == 0)
		_Unwind_DeleteException(&cxxe->ue);

}
EXPORT_CSYMBOL(__cxa_end_catch);

extern "C" bool __gxx_personality_v0(int, int, unsigned long long, struct UnwindException* ue, void*)
{
	struct CxxEx lsda;
	const unsigned char* p = (const unsigned char*) uc.lsda;
	if (p == NULL)
		return false;
	uleb128_t tmp;
	p++;
	if (*p++ != 0xff) {
		p = readUleb128(p, &tmp);
		lsda.tiBase = reinterpret_cast<const type_info* const*>(p + tmp);
	}
	p++;
	p = readUleb128(p, &tmp);
	lsda.csBase = reinterpret_cast<const struct CallSite*>(p);
	lsda.codeBase = uc.pcBegin;
	lsda.arBase = reinterpret_cast<const struct ActionRecord*>(p + tmp);

	ptr_t ip = reinterpret_cast<ptr_t>(uc.ip), landingPad = NULL;
	const unsigned char* arBase = NULL;

	while (p < (unsigned char*) lsda.arBase) {
		struct CallSite cs;
		p = readUleb128(p, &cs.start);
		p = readUleb128(p, &cs.len);
		p = readUleb128(p, &cs.lp);
		p = readUleb128 (p, &cs.action);

		if (ip < lsda.codeBase + cs.start)
			break;
		else if (ip < lsda.codeBase + cs.start + cs.len) {
			if (cs.lp)
				landingPad = lsda.codeBase + cs.lp;
			if (cs.action)
				arBase = (unsigned char*) lsda.arBase + cs.action - 1;
			break;
		}
	}

	struct ActionRecord ar;
	if (landingPad == NULL) {
		return false;
	} else if (arBase == NULL) {
		// clean-up route.
	} else {
		const type_info *catch_type, *throw_type = containerof(ue, CXXException, ue)->exceptionType;
		ar.disp = 1;
		for (p = arBase; ar.disp != 0; p += ar.disp) {
			p = readSleb128(p, &ar.filter);
			readSleb128(p, &ar.disp);
			if (ar.filter == 0) {
				// clean-up.
			} else if (ar.filter > 0) {
				const std::type_info* const* ttmp = lsda.tiBase - ar.filter;
				if (ABIBase::top()->pic)
					ttmp = reinterpret_cast<const std::type_info**>(reinterpret_cast<ptr_t>(ttmp) + reinterpret_cast<ptrdiff_t>(*ttmp));
				catch_type = *ttmp;
				// NULL means `catch(...)'.
				if (catch_type == NULL || throw_type->upCast(catch_type))
					break;
			}
		}
	}
	uc.eax = reinterpret_cast<unsigned int>(ue);
	uc.edx = ar.filter;
	uc.ip = reinterpret_cast<unsigned char*>(landingPad);
	return true;
}
EXPORT_CSYMBOL(__gxx_personality_v0);
// }])>
}
// vim: foldmarker=<([{,}])> foldmethod=marker
